﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel.Dispatcher;
using WcfFlashRemoting.ServiceModel.Channels;
using System.ServiceModel;
using System.Runtime.Serialization;

namespace WcfFlashRemoting.ServiceModel.Dispatcher
{
    class AmfMultipleInvoker : IOperationInvoker
    {
        DispatchRuntime dispatchRuntime;
        public AmfMultipleInvoker(DispatchRuntime dispatchRuntime)
        {
            this.dispatchRuntime = dispatchRuntime;
        }

        private const string SuccessSuffix = "/onResult";
        private const string FaultSuffix = "/onStatus";

        #region IOperationInvoker Members

        public object[] AllocateInputs()
        {
            return new object[1];
        }

        public object Invoke(object instance, object[] inputs, out object[] outputs)
        {
            List<AmfMessage> results = new List<AmfMessage>();
            IEnumerable<AmfMessage> msgs = (inputs[0] as IEnumerable<AmfMessage>);
            foreach (AmfMessage msg in msgs)
            {
                DispatchOperation op = dispatchRuntime.Operations[msg.AmfBody.Target.Split('.').Last()];
                AmfMessage result = PerformOperation(instance, msg, op);
                results.Add(result);
            }
            outputs = new object[0];
            return results;
        }

        private AmfMessage PerformOperation(object instance, AmfMessage msg, DispatchOperation op)
        {
            object[] inputs = op.Invoker.AllocateInputs();
            try
            {
                op.Formatter.DeserializeRequest(msg, inputs);
            }
            catch 
            {
                // TODO - do something with exception?
                AmfMessage fault = new AmfMessage(msg.Version, new AmfObjects.AmfHeader[0], new AmfObjects.AmfBody(), msg.AmfVersion);
                fault.AmfBody.Content = new AmfObjects.AmfFault()
                {
                    code = "REQUEST.CONTRACT",
                    level = "error",
                    description = "Request did not conform to contract.",
                    details = null,
                    type = "Request Contract"
                };
                fault.AmfBody.Target = msg.AmfBody.Response + FaultSuffix;
                return fault;
            }
            object[] outputs;
            try
            {
                object returnValue = op.Invoker.Invoke(instance, inputs, out outputs);
                AmfMessage result = op.Formatter.SerializeReply(msg.Version, outputs, returnValue) as AmfMessage;
                result.AmfBody.Target = msg.AmfBody.Response + SuccessSuffix;
                return result;
            }
            catch (FaultException ex)
            {
                Type t = ex.GetType();
                while (t != typeof(FaultException) && t.GetGenericTypeDefinition() != typeof(FaultException<>))
                {
                    t = t.BaseType;
                }
                if (t.GetGenericTypeDefinition() == typeof(FaultException<>))
                {
                    AmfMessage fault = new AmfMessage(msg.Version, new AmfObjects.AmfHeader[0], new AmfObjects.AmfBody(), msg.AmfVersion);
                    Type genType = t.GetGenericArguments()[0];
                    fault.AmfBody.Content = new AmfObjects.AmfFault()
                    {
                        code = ex.Code.ToString(),
                        level = "error",
                        description = ex.Reason.ToString(),
                        details = t.GetProperty("Detail").GetValue(ex, null),
                        type = (genType.GetCustomAttributes(typeof(DataContractAttribute), false)[0] as DataContractAttribute).Name ?? genType.Name
                    };
                    fault.AmfBody.Target = msg.AmfBody.Response + FaultSuffix;
                    return fault;
                }
                else
                {
                    throw;
                }
            }
        }

        public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
        {
            throw new NotImplementedException();
        }

        public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
        {
            throw new NotImplementedException();
        }

        public bool IsSynchronous
        {
            get { return true; }
        }

        #endregion
    }
}
